/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.wsonrpc.json.support.jackson;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import net.apexes.wsonrpc.core.exception.JsonException;
import net.apexes.wsonrpc.json.JsonImplementor;
import net.apexes.wsonrpc.json.JsonRpcError;
import net.apexes.wsonrpc.json.JsonRpcMessage;
import net.apexes.wsonrpc.json.JsonRpcRequest;
import net.apexes.wsonrpc.json.JsonRpcResponse;

import java.io.IOException;

/**
 * 
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 */
public class JacksonImplementor implements JsonImplementor {
    
    protected final ObjectMapper objectMapper;
    
    public JacksonImplementor() {
        this(new ObjectMapper());
    }
    
    public JacksonImplementor(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    protected JsonRpcRequest createJsonRpcRequest(ObjectMapper objectMapper, ObjectNode objectNode) {
        return new JacksonJsonRpcRequest(objectMapper, objectNode);
    }

    protected JsonRpcResponse createJsonRpcResponse(ObjectMapper objectMapper, ObjectNode objectNode) {
        return new JacksonJsonRpcResponse(objectMapper, objectNode);
    }

    @Override
    public JsonRpcMessage fromJson(String json) throws JsonException {
        try {
            ObjectNode objectNode = (ObjectNode) objectMapper.readTree(json);
            if (objectNode.has("method")) {
                return createJsonRpcRequest(objectMapper, objectNode);
            }
            return createJsonRpcResponse(objectMapper, objectNode);
        } catch (IOException e) {
            throw new JsonException(e.getMessage(), e.getCause());
        }
    }

    @Override
    public JsonRpcRequest createRequest(String id, String method, Object[] params) {
        ObjectNode objectNode = objectMapper.getNodeFactory().objectNode();
        objectNode.put("jsonrpc", "2.0");
        objectNode.put("id", id);
        objectNode.put("method", method);
        if (params != null) {
            ArrayNode arrayNode = objectNode.putArray("params");
            for (Object param : params) {
                arrayNode.add(objectMapper.getNodeFactory().pojoNode(param));
            }
        }
        return createJsonRpcRequest(objectMapper, objectNode);
    }

    @Override
    public JsonRpcRequest createNotice(String method, Object[] params, String trace) {
        ObjectNode objectNode = objectMapper.getNodeFactory().objectNode();
        objectNode.put("jsonrpc", "2.0");
        if (trace != null) {
            objectNode.put("trace", trace);
        }
        objectNode.put("method", method);
        if (params != null) {
            ArrayNode arrayNode = objectNode.putArray("params");
            for (Object param : params) {
                arrayNode.add(objectMapper.getNodeFactory().pojoNode(param));
            }
        }
        return createJsonRpcRequest(objectMapper, objectNode);
    }

    @Override
    public JsonRpcResponse createResponse(String id, Object result) {
        JsonNode resultNode = objectMapper.getNodeFactory().pojoNode(result);
        return createResponse(id, "result", resultNode);
    }

    @Override
    public JsonRpcResponse createResponse(JsonRpcError error) {
        return createResponse(null, error);
    }

    @Override
    public JsonRpcResponse createResponse(String id, JsonRpcError error) {
        ObjectNode errorNode = objectMapper.getNodeFactory().objectNode();
        errorNode.put("code", error.getCode());
        if (error.getMessage() != null) {
            errorNode.put("message", error.getMessage());
        }
        if (error.getData() != null) {
            errorNode.set("data", objectMapper.getNodeFactory().pojoNode(error.getData()));
        }
        return createResponse(id, "error", errorNode);
    }

    private JsonRpcResponse createResponse(String id, String fieldName, JsonNode jsonNode) {
        ObjectNode objectNode = objectMapper.getNodeFactory().objectNode();
        objectNode.put("jsonrpc", "2.0");
        if (id != null) {
            objectNode.put("id", id);
        }
        objectNode.set(fieldName, jsonNode);
        return createJsonRpcResponse(objectMapper, objectNode);
    }

}
